﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows.Controls;
using Microsoft.Xrm.Sdk.Client;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using System.Threading;
using System.Windows.Threading;
using System.ComponentModel;

namespace VisualRibbonEditor
{
	/// <summary>
	/// WebResource manager
	/// </summary>
	public static class WebResourceManager
	{
		public static bool IsLoaded { get; internal set; }
		public delegate void WebResourcesLoadedHandler();
		public static event WebResourcesLoadedHandler Loaded;
		//dictionary used for caching webresource contents 
		private static Dictionary<string, byte[]> _cache = new Dictionary<string, byte[]>();

		//cached list of names of all webresources
		static string[] _webResouceNames = null;
		private static SynchronizedCollection<KeyValuePair<string, WebResourceType>> _webResources = new SynchronizedCollection<KeyValuePair<string, WebResourceType>>();

		private static void DoLoad()
		{
			lock (_webResources.SyncRoot)
			{
				if (IsLoaded) return;
				IsLoaded = false;
				
				var svc = Helpers.GetOrgService();
				QueryExpression q = new QueryExpression();
				q.EntityName = "webresource";
				q.ColumnSet = new ColumnSet();
				q.ColumnSet.AddColumn("name");
				q.ColumnSet.AddColumn("webresourcetype");

				EntityCollection ec = svc.RetrieveMultiple(q);

				string webResourceName;
				List<string> names = new List<string>();

				foreach (var ent in ec.Entities)
				{
					webResourceName = ent["name"].ToString();
					names.Add("$webresource:" + webResourceName);
					_webResources.Add(new KeyValuePair<string, WebResourceType>(webResourceName, (WebResourceType)((OptionSetValue)ent["webresourcetype"]).Value));
				}
				//cache the names
				_webResouceNames = names.ToArray();
				IsLoaded = true;
			}
			if (Loaded != null) Loaded();
		}

		static WebResourceManager()
		{
			IsLoaded = false;
		}

		public static void ClearCache()
		{
			lock (_webResources.SyncRoot)
			{
				_webResouceNames = null;
				_webResources.Clear();
				_cache.Clear();
				IsLoaded = false;
			}
		}
		public static void GetWebResourceContent(string name, Action<byte[]> callback)
		{
			name = name.ToLowerInvariant();
			if (_cache.ContainsKey(name))
			{
				//found the data in the local cache.
				callback(_cache[name]);
			}
			else
			{
				//retrieve the data (in background thread) and then cache it.
				RetrieveWebResource(name, (bits) =>
				{
					if (bits != null) _cache[name] = bits;
					callback(bits);
				});
			}
		}

		/// <summary>
		/// Retrieves the contents of a webresource in a background thread.
		/// </summary>
		private static void RetrieveWebResource(string name, Action<byte[]> callback)
		{
			Dispatcher dispatcher = Dispatcher.CurrentDispatcher;
			BackgroundWorker worker = new BackgroundWorker();
			worker.DoWork += (sender, e) =>
			{
				//retrieve the webresource from CRM in a background thread
				var svc = Helpers.GetOrgService();
				QueryExpression q = new QueryExpression
				{
					EntityName = "webresource",
					ColumnSet = new ColumnSet { AllColumns = true }
				};
				q.Criteria = new FilterExpression();
				ConditionExpression cond = new ConditionExpression("name", ConditionOperator.Equal, name);
				q.Criteria.Conditions.Add(cond);

				svc.RetrieveMultipleAsync(q, (ec) =>
				{
					Byte[] bits = null;
					if (ec.Entities.Count > 0)
					{
						Entity webResouce = ec.Entities[0];
						string content = webResouce["content"].ToString();
						bits = Convert.FromBase64String(content);
					}

					dispatcher.Invoke(callback, bits);
				});
			};

			worker.RunWorkerAsync();
		}

		public static void Load()
		{
			if (IsLoaded) return;
			DoLoad();
		}

		public static void LoadAsync(Action callback)
		{
			Dispatcher dispatcher = Dispatcher.CurrentDispatcher;
			BackgroundWorker worker = new BackgroundWorker();
			worker.DoWork += (sender, e) =>
			{
				DoLoad();
				dispatcher.Invoke(callback);
			};
			worker.RunWorkerAsync();
		}

		public static void GetAllWebResourceNamesAsync(Action<string[]> callback)
		{
			LoadAsync(() => { callback(_webResouceNames); });
		}

		public static void GetAllWebResourcesAsync(Action<SynchronizedCollection<KeyValuePair<string, WebResourceType>>> callback)
		{
			LoadAsync(() => { callback(_webResources); });
		}

		/// <summary>
		/// Sets up an autoCompleteBox to use a list of all image (.png) webresources on the current CRM org. 
		/// </summary>
		/// <param name="boxes">list of autocompleteboxes to set up.</param>
		public static void SetupImageAutoComplete(AutoCompleteBox[] boxes)
		{
			WebResourceManager.GetAllWebResourcesAsync(webResources =>
			{
				foreach (var box in boxes)
				{
					box.FilterMode = AutoCompleteFilterMode.Custom;
					box.ItemsSource = webResources.Where(x => (x.Value == WebResourceType.PNGformat || x.Value == WebResourceType.GIFformat || x.Value == WebResourceType.ICOformat || x.Value == WebResourceType.JPGformat)).Select(k => "$webresource:" + k.Key);
					box.ItemFilter = new AutoCompleteFilterPredicate<object>(FilterByName_Contains);
				}
			});
		}

		public static void SetupScriptAutoComplete(AutoCompleteBox box)
		{
			WebResourceManager.GetAllWebResourcesAsync(webResources =>
			{
				box.FilterMode = AutoCompleteFilterMode.Custom;
				box.ItemsSource = webResources.Where(x => x.Value == WebResourceType.Script_JScript).Select(k => "$webresource:" + k.Key);
				box.ItemFilter = new AutoCompleteFilterPredicate<object>(FilterByName_Contains);
			});
		}

		private static bool FilterByName_Contains(string s, object o)
		{
			string item = o.ToString().Replace("$webresource:", "").ToLowerInvariant();
			s = s.ToLowerInvariant().Replace("$webresource:", "");

			if (item.Contains(s)) return true;

			int lastSlash = item.LastIndexOf('/');
			if (lastSlash > 0)
			{
				item = item.Substring(lastSlash + 1);
				if (item.Contains(s)) return true;
			}
			return false;
		}

		private static bool FilterByName_StartsWith(string s, object o)
		{
			string item = o.ToString().Replace("$webresource:", "").ToLowerInvariant();
			s = s.ToLowerInvariant().Replace("$webresource:", "");

			if (item.StartsWith(s))
			{
				return true;
			}

			int lastSlash = item.LastIndexOf('/');
			if (lastSlash > 0)
			{
				item = item.Substring(lastSlash + 1);
				if (item.StartsWith(s))
				{
					return true;
				}
			}

			return false;
		}
	}
}
